// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart'
    hide Element, ElementKind;
import 'package:analyzer_plugin/src/utilities/documentation.dart';
import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
import 'package:analyzer_plugin/utilities/completion/relevance.dart';
import 'package:analyzer_plugin/utilities/completion/suggestion_builder.dart';

/// An object used to build code completion suggestions for Dart code.
class SuggestionBuilderImpl implements SuggestionBuilder {
  /// The resource provider used to access the file system.
  final ResourceProvider? resourceProvider;

  /// The converter used to convert analyzer objects to protocol objects.
  final AnalyzerConverter converter = AnalyzerConverter();

  /// Initialize a newly created suggestion builder.
  SuggestionBuilderImpl(this.resourceProvider);

  /// Add default argument list text and ranges based on the given
  /// [requiredParams] and [namedParams].
  void addDefaultArgDetails(
    CompletionSuggestion suggestion,
    Element element,
    Iterable<FormalParameterElement> requiredParams,
    Iterable<FormalParameterElement> namedParams,
  ) {
    // Copied from analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
    var buffer = StringBuffer();
    var ranges = <int>[];

    int offset;

    for (var param in requiredParams) {
      if (buffer.isNotEmpty) {
        buffer.write(', ');
      }
      offset = buffer.length;
      var name = param.name ?? '';
      buffer.write(name);
      ranges.addAll([offset, name.length]);
    }

    for (var param in namedParams) {
      if (param.metadata.hasRequired || param.isRequiredNamed) {
        if (buffer.isNotEmpty) {
          buffer.write(', ');
        }
        var name = param.name ?? '';
        buffer.write('$name: ');
        offset = buffer.length;
        buffer.write(name);
        ranges.addAll([offset, name.length]);
      }
    }

    suggestion.defaultArgumentListString = buffer.isNotEmpty
        ? buffer.toString()
        : null;
    suggestion.defaultArgumentListTextRanges = ranges.isNotEmpty
        ? ranges
        : null;
  }

  @override
  CompletionSuggestion? forElement(
    Element? element, {
    String? completion,
    CompletionSuggestionKind? kind,
    int relevance = DART_RELEVANCE_DEFAULT,
  }) {
    // Copied from analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
    if (element == null) {
      return null;
    }
    if (element is MethodElement && element.isOperator) {
      // Do not include operators in suggestions
      return null;
    }
    completion ??= element.displayName;

    var isDeprecated = element.metadata.hasDeprecated;
    var suggestion = CompletionSuggestion(
      kind ?? CompletionSuggestionKind.INVOCATION,
      isDeprecated ? DART_RELEVANCE_LOW : relevance,
      completion,
      completion.length,
      0,
      isDeprecated,
      false,
    );

    // Attach docs.
    var doc = removeDartDocDelimiters(element.documentationComment);
    suggestion.docComplete = doc;
    suggestion.docSummary = getDartDocSummary(doc);

    suggestion.element = converter.convertElement(element);
    var enclosingElement = element.enclosingElement;
    if (enclosingElement is ClassElement) {
      suggestion.declaringType = enclosingElement.displayName;
    }
    suggestion.returnType = getReturnTypeString(element);
    if (element is ExecutableElement && element is! PropertyAccessorElement) {
      suggestion.parameterNames = element.formalParameters
          .map((parameter) => parameter.name ?? '')
          .toList();
      suggestion.parameterTypes = element.formalParameters.map((parameter) {
        return parameter.type.getDisplayString();
      }).toList();

      var requiredParameters = element.formalParameters.where(
        (param) => param.isRequiredPositional,
      );
      suggestion.requiredParameterCount = requiredParameters.length;

      var namedParameters = element.formalParameters.where(
        (param) => param.isNamed,
      );
      suggestion.hasNamedParameters = namedParameters.isNotEmpty;

      addDefaultArgDetails(
        suggestion,
        element,
        requiredParameters,
        namedParameters,
      );
    }
    return suggestion;
  }

  String? getReturnTypeString(Element element) {
    // Copied from analysis_server/lib/src/protocol_server.dart
    if (element is ExecutableElement) {
      if (element.kind == ElementKind.SETTER) {
        return null;
      } else {
        return element.returnType.getDisplayString();
      }
    } else if (element is VariableElement) {
      var type = element.type;
      return type.getDisplayString();
    } else if (element is TypeAliasElement) {
      if (element.aliasedType case FunctionType aliasedType) {
        return aliasedType.returnType.getDisplayString();
      }
      return null;
    } else {
      return null;
    }
  }
}
